import { makeAutoObservable, reaction } from 'mobx';
import { v4 as uuidv4 } from 'uuid';
import naturalCompare from 'string-natural-compare';
import TestCase from './TestCase';

export const DEFAULT_TIMEOUT = 300;

export default class Suite {
    id = null;

    name = null;

    timeout = DEFAULT_TIMEOUT;

    isParallel = false;

    persistSession = false;

    _tests = [];

    modified = false;

    isOpen = false;

    constructor(id = uuidv4(), name = 'Untitled Suite') {
        this.id = id;
        this.name = name;
        this.changeTestsDisposer = reaction(
            () => this._tests.length,
            () => {
                this.modified = true;
            },
        );
        this.containsTest = this.containsTest.bind(this);
        this.export = this.export.bind(this);

        makeAutoObservable(this, {}, { autoBind: true });
    }

    get tests() {
        return this.isParallel ? this._tests.slice().sort((t1, t2) => naturalCompare(t1.name, t2.name)) : this._tests;
    }

    isTest(test) {
        return test && test instanceof TestCase;
    }

    setName(name) {
        this.name = name;
        this.modified = true;
    }

    setTimeout(timeout = DEFAULT_TIMEOUT) {
        if (timeout !== undefined && timeout.constructor.name !== 'Number') {
            throw new Error(`Expected to receive Number instead received ${timeout !== undefined ? timeout.constructor.name : timeout}`);
        } else {
            this.timeout = timeout;
        }
    }

    setParallel(parallel) {
        this.isParallel = !!parallel;
        if (this.isParallel) {
            // setting directly because setPersistSession is locked
            this.persistSession = false;
        }
    }

    setPersistSession(persistSession) {
        if (!this.isParallel) {
            this.persistSession = persistSession;
        }
    }

    setOpen(isOpen) {
        this.isOpen = isOpen;
    }

    containsTest(test) {
        // WARNING: do not turn this into a bound action, the observables it will set will cause react-dnd to fail!
        return this._tests.includes(test);
    }

    addTestCase(test) {
        if (!this.isTest(test)) {
            throw new Error(`Expected to receive TestCase instead received ${test ? test.constructor.name : test}`);
        } else {
            this._tests.push(test);
        }
    }

    insertTestCaseAt(test, index) {
        if (!this.isTest(test)) {
            throw new Error(`Expected to receive TestCase instead received ${test ? test.constructor.name : test}`);
        } else {
            this._tests.splice(index, 0, test);
        }
    }

    removeTestCase(test) {
        if (!this.isTest(test)) {
            throw new Error(`Expected to receive TestCase instead received ${test ? test.constructor.name : test}`);
        } else {
            this._tests.remove(test);
        }
    }

    swapTestCases(from, to) {
        const test = this._tests.splice(from, 1)[0];
        this.insertTestCaseAt(test, to);
    }

    replaceTestCases(tests) {
        if (tests.filter((test) => !this.isTest(test)).length) {
            throw new Error('Expected to receive array of TestCase');
        } else {
            this._tests.replace(tests);
        }
    }

    export() {
        return {
            id: this.id,
            name: this.name,
            persistSession: this.persistSession,
            parallel: this.isParallel,
            timeout: this.timeout,
            tests: this._tests.map((t) => t.id),
        };
    }

    static fromJS = function (jsRep, projectTests) {
        const suite = new Suite(jsRep.id);
        suite.setName(jsRep.name);
        suite.setTimeout(jsRep.timeout);
        suite.setParallel(jsRep.parallel);
        suite.setPersistSession(jsRep.persistSession);
        suite._tests.replace(jsRep.tests.map((testId) => projectTests.find(({ id }) => id === testId)));

        return suite;
    };
}
